package tasks;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

import tools.Format;
import tools.Utils;
import core.Executor;
import core.Message;
import core.Peer;
import core.Piece;
import core.PieceQueue;
import core.Torrent;
import core.TorrentManager;
import java.io.*;

public class ReadMessage implements Runnable {

	private Peer peer;
	private Torrent torrent;

	private boolean closed;

	public ReadMessage(Peer peer, Torrent torrent) {
		this.peer = peer;
		this.torrent = torrent;
		this.closed = false;
	}

	@Override
	public void run() {

		int length_message = -1;
		int code;
		byte[] length;
		byte[] id;

		do {

			try {
				length = new byte[4];
				id = new byte[1];

				this.handleRead(this.peer.getInput().read(length, 0, 4));

				length_message = Format.byteArrayToInt(length);

				if (length_message == 0) {
					this.peer.getSocket().setKeepAlive(true);
				} else if (length_message >= 1) {
					this.handleRead(this.peer.getInput().read(id, 0, 1));
					code = ByteBuffer.allocate(4).put(id).order(ByteOrder.LITTLE_ENDIAN).getInt(0);

					byte[] buffer = new byte[4];
					switch (code) {
					case 0:
						this.peer.setChoked(true);
						break;
					case 1:
						this.peer.setChoked(false);
						break;
					case 2:
						this.peer.setInterested(true);
						break;
					case 3:
						this.peer.setInterested(false);
						break;
					case 4:
						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						
						int indexHave = ByteBuffer.allocate(4).put(buffer).getInt(0);
						byte[] byteIndex = Format.intToByteArray(indexHave);
						byte[] byteBegin = Format.intToByteArray(0);

						byte[] byteLength;
						if(this.torrent.getPieces().length-1 == indexHave) {
							byteLength =Format.intToByteArray((int) (this.torrent.getLength()
									-(this.torrent.getPieceLength()*(this.torrent.getPieces().length-1))));
						} else {
							byteLength =Format.intToByteArray(this.torrent.getPieceLength());
						}
						byte[] payload = Utils.concat(byteIndex,Utils.concat(byteBegin,byteLength));
						Utils.setBit(this.peer.getBitfield(),indexHave,1);
						 if (Utils.getBit(this.torrent.getBitfield(),indexHave) ==0 ){
							 Executor.getThreadPool().execute(new SendMessage(new Message(6,payload,this.peer.getOutput())));
						 }
						
						break;
					case 5:
						byte[] bitField = new byte[length_message - 1];
						this.handleRead(this.peer.getInput().read(bitField, 0, (length_message - 1)));
						this.peer.setBitfield(bitField);

						break;
					case 6:
						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int indexRequest = ByteBuffer.allocate(4).put(buffer).getInt(0);

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int beginRequest = ByteBuffer.allocate(4).put(buffer).getInt(0);

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int lengthRequest = ByteBuffer.allocate(4).put(buffer).getInt(0);

						int pieceLength = this.torrent.getPieceLength();


						if (lengthRequest <= pieceLength) {

							RandomAccessFile file = new RandomAccessFile(this.torrent.getName(), "r");
                                                        file.seek((indexRequest*this.torrent.getPieceLength())+beginRequest);
                                                        

							byte[] b = new byte[lengthRequest];
							file.read(b);

							byte[] bufferindex = ByteBuffer.allocate(4).putInt(indexRequest).order(ByteOrder.LITTLE_ENDIAN).array();
							byte[] bufferbegin = ByteBuffer.allocate(4).putInt(beginRequest).order(ByteOrder.LITTLE_ENDIAN).array();

							Executor.getThreadPool().execute(new SendMessage(new Message(7, Utils.concat(bufferindex, Utils.concat(bufferbegin, b)), this.peer.getOutput())));
                                                       this.torrent.increaseUploaded(b.length);
						}

						break;
					case 7:

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int indexPiece = ByteBuffer.allocate(4).put(buffer).getInt(0);

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int beginPiece = ByteBuffer.allocate(4).put(buffer).getInt(0);

						int lavraielength = Format.byteArrayToInt(length);
						byte[] block = new byte[lavraielength-9];

						this.peer.getInput().read(block);
						MessageDigest sha1 = null;
						try {
							sha1 = MessageDigest.getInstance("SHA-1");
						} catch (NoSuchAlgorithmException e) {
							e.printStackTrace();
						}
						DigestInputStream   dis = new DigestInputStream(
								new BufferedInputStream(
										new ByteArrayInputStream(block)), sha1);

						while (dis.read() != -1);				        

						String hash = Format.byteArray2Hex(sha1.digest());						
						String hashPiece = Format.byteArray2Hex(Arrays.copyOfRange(this.torrent.getPieces(), indexPiece*20, indexPiece*20+20));


						if (hash.equals(hashPiece)) {
							if (this.torrent.isMulti()) {
								System.out.println("Fuuuu");
							} else {

								PieceQueue.getPieceQueue().add(new Piece(block, indexPiece, this.torrent.getName(),this.torrent.getPieceLength()));
								Utils.setBit(this.torrent.getBitfield(),indexPiece,1);
                                this.torrent.increaseDownloaded(block.length);
                                
								
								//La partie suivante est censé envoyer un "have" à chaque peers connecté sur ce torrent de manière à les tenir informés de notre avancement, hors le "have" ici présent n'est pas tout à fait opérationnel
								/*ArrayList<Peer> liste_peers = TorrentManager.getInstance().getPeersByTorrent(this.torrent.getHashInfo());
								
								if(liste_peers != null) {
									for (int i = 0; i < liste_peers.size(); i++) {
										//Executor.getThreadPool().execute(new SendMessage(new Message(4, Format.intToByteArray(indexPiece), liste_peers.get(i).getOutput())));
									}
								}*/								
							}							

						} else {
							this.peer.close();
							TorrentManager.getInstance().removePeerByTorrent(this.torrent.getHashInfo(), this.peer);							
						}

						break;
					case 8:

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int indexCancel = ByteBuffer.allocate(4).put(buffer).getInt(0);

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int beginCancel = ByteBuffer.allocate(4).put(buffer).getInt(0);

						Arrays.fill(buffer, (byte) 0);
						this.handleRead(this.peer.getInput().read(buffer, 0, 4));
						int lengthCancel = ByteBuffer.allocate(4).put(buffer).getInt(0);

						break;
					default:
						this.close();
						break;
					}


				}

			} catch (IOException e) {
				this.closed = true;
				System.out.println("ReadMessage|IOException: "+e);
			} catch (Exception e) {
				System.out.println("ReadMessage|Exception: "+e);
			}


		} while (length_message > 0 && !closed);
	}

	public void handleRead(int readReturn) throws IOException {
		if (readReturn == -1) {
			this.close();
		}
	}

	public void close() {
		try {
			this.peer.getInput().close();
			this.peer.getOutput().close();
			this.peer.getSocket().close();

			this.closed = true;
			TorrentManager.getInstance().removePeerByTorrent(this.peer.getInfoHash(), this.peer);
		} catch (IOException e) {
			System.out.println("ReadMessage|close()|IOException: " + e);
		} catch (Exception e) {
			System.out.println("ReadMessage|close()|Exception: " + e);
		}
	}
}
